package main

import "fmt"

const fileFilter = "MP4 File(*.mp4);;All File(*)"

type Root = Mp4FileBox

type Mp4FileBox []Box[FileBody]

type Box[T any] struct {
	Header BoxHeader
	Body   T
}

func (v *Box[T]) Unmarshal(dec *Decoder, info *ValueInfo) {
	dec.Decode(&v.Header, &ValueInfo{name: "Header", isBig: true})
	newDec := dec.SubDecoder(v.Header.BodySize())
	newDec.SetArg(&v.Header)
	newDec.Decode(&v.Body, &ValueInfo{name: "Body", isBig: true})
	dec.Seek(newDec.Pos())
}

func (v *Box[T]) String() string {
	return string(v.Header.Type)
}

type BoxHeader struct {
	Size      uint32
	Type      Str32
	LargeSize uint64
	Version   uint8
	Flags     uint32
}

func (v *BoxHeader) Unmarshal(dec *Decoder, info *ValueInfo) {
	dec.Decode(&v.Size, &ValueInfo{name: "Size", isBig: true})
	dec.Decode(&v.Type, &ValueInfo{name: "Type", isBig: true})
	if v.Size == 1 {
		dec.Decode(&v.LargeSize, &ValueInfo{name: "LargeSize", isBig: true})
	}
	if v.IsFullBox() {
		dec.Decode(&v.Version, &ValueInfo{name: "Version", isBig: true})
		dec.Decode(&v.Flags, &ValueInfo{name: "Flags", isBig: true, bit: 24})
	}
}

func (v *BoxHeader) IsFullBox() bool {
	switch v.Type {
	case "mvhd", "tkhd", "mdhd", "hdlr", "vmhd", "smhd", "url ", "urn ":
		return true
	case "dref", "stsd", "ctts", "stts", "stsz", "stsc", "stss", "stco", "co64":
		return true
	default:
		return false
	}
}

func (v *BoxHeader) BodySize() int {
	n := 0
	if v.Size == 1 {
		n = int(v.LargeSize) - 16
	} else {
		n = int(v.Size) - 8
	}
	if v.IsFullBox() {
		n -= 4
	}
	return n
}

type Str32 string

func (v *Str32) Unmarshal(dec *Decoder, info *ValueInfo) {
	*v = Str32(dec.ReadBytes(4))
}

type FileBody struct {
	V any
}

func (v *FileBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "ftyp":
		var tmp FileTypeBody
		v.V = &tmp
	case "mdat":
		var tmp MediaDataBody
		v.V = &tmp
	case "free":
		var tmp FreeSpaceBody
		v.V = &tmp
	case "skip":
		var tmp FreeSpaceBody
		v.V = &tmp
	case "moov":
		var tmp []Box[MovieBody]
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type FileTypeBody struct {
	MajorBrand       Str32
	MinorVersion     uint32
	CompatibleBrands []Str32
}
type MediaDataBody []uint8
type FreeSpaceBody []uint8
type DefaultBody []uint8

type MovieBody FileBody

func (v *MovieBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "mvhd":
		var tmp MovieHeaderBody
		v.V = &tmp
	case "trak":
		var tmp []Box[TrackBody]
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type MovieHeaderBody struct {
	CreationTime    Uint64V1 `info:"创建时间"`
	MdificationTime Uint64V1 `info:"修改时间"`
	Timescale       uint32   `info:"时间刻度/秒"`
	Duration        Uint64V1 `info:"总时间刻度"`
	Rate            Fract32  `info:"推荐速率"`
	Volume          Fract16  `info:"推荐音量"`
	Reserved        uint16
	Reserved2       [2]uint32
	Matrix          [9]uint32 `info:"视频变换矩阵"`
	PreDefined      [6]uint32
	NextTrackID     uint32 `info:"下个track id"`
}

type Uint64V1 uint64

func (v *Uint64V1) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	if hdr.Version == 1 {
		var tmp uint64
		dec.Decode(&tmp, info)
		*v = Uint64V1(tmp)
	} else {
		var tmp uint32
		dec.Decode(&tmp, info)
		*v = Uint64V1(tmp)
	}
}

type Fract32 uint32

func (v Fract32) String() string {
	return fmt.Sprintf("%d.%d", v>>16, v&0xFFFF)
}

type Fract16 uint16

func (v Fract16) String() string {
	return fmt.Sprintf("%d.%d", v>>8, v&0xFF)
}

type TrackBody FileBody

func (v *TrackBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "tkhd":
		var tmp TrackHeaderBody
		v.V = &tmp
	case "mdia":
		var tmp []Box[MediaBody]
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type TrackHeaderBody struct {
	CreationTime     Uint64V1
	ModificationTime Uint64V1
	TrackId          uint32
	Reserved         uint32
	Duration         Uint64V1
	Reserved2        [2]uint32
	Layer            uint16  `info:"视频层"`
	AlternateGroup   uint16  `info:"分组"`
	Volume           Fract16 `info:"音量"`
	Reserved3        uint16
	Matrix           [9]uint32 `info:"视频变换矩阵"`
	Width            Fract32
	Height           Fract32
}

type MediaBody FileBody

func (v *MediaBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "mdhd":
		var tmp MediaHeaderBody
		v.V = &tmp
	case "hdlr":
		var tmp HandlerBody
		v.V = &tmp
	case "minf":
		var tmp []Box[MediaInformationBody]
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type MediaHeaderBody struct {
	CreationTime     Uint64V1
	ModificationTime Uint64V1
	Timescale        uint32
	Duration         Uint64V1
	Pad              uint8    `bit:"1"`
	Language         [3]uint8 `bit:"5" info:"语言码"`
	PreDefined       uint16
}

type HandlerBody struct {
	PreDefined  uint32
	HandlerType Str32
	Reserved    [3]uint32
	Name        string
}

type MediaInformationBody FileBody

func (v *MediaInformationBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "vmhd":
		var tmp VideoMediaHeaderBody
		v.V = &tmp
	case "smhd":
		var tmp SoundMediaHeaderBody
		v.V = &tmp
	case "dinf":
		var tmp []Box[DataInformation]
		v.V = &tmp
	case "stbl":
		var tmp []Box[SampleTableBody]
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type VideoMediaHeaderBody struct {
	Graphicsmode uint16 `info:"合成模式"`
	Opcolor      [3]uint16
}

type SoundMediaHeaderBody struct {
	Balance  uint16 `info:"立体声平衡"`
	Reserved uint16
}

type DataInformation FileBody

func (v *DataInformation) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "dref":
		var tmp DataReferenceBody
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type Directory[T any] struct {
	EntryCount uint32
	DataEntry  []T
}

func (v *Directory[T]) Unmarshal(dec *Decoder, info *ValueInfo) {
	dec.Decode(&v.EntryCount, &ValueInfo{name: "EntryCount", isBig: true})
	v.DataEntry = make([]T, v.EntryCount)
	dec.Decode(&v.DataEntry, &ValueInfo{name: "DataEntry", isBig: true})
}

type DataReferenceBody = Directory[Box[DataEntryBody]]
type DataEntryBody FileBody

func (v *DataEntryBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	if dec.IsEof() {
		return
	}
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "url ":
		var tmp DataEntryUrlBody
		v.V = &tmp
	case "urn ":
		var tmp DataEntryUrnBody
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type DataEntryUrlBody struct {
	Location string
}

type DataEntryUrnBody struct {
	Name     string
	Location string
}

type SampleTableBody FileBody

func (v *SampleTableBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	hdr, ok := dec.Arg().(*BoxHeader)
	if !ok || hdr == nil {
		panic("decode arg not set")
	}

	switch hdr.Type {
	case "stsd":
		var tmp SampleDescriptionBody
		v.V = &tmp
	case "ctts":
		var tmp CompositionOffsetBody
		v.V = &tmp
	case "stts":
		var tmp TimeToSampleBody
		v.V = &tmp
	case "stsz":
		var tmp SampleSizeBody
		v.V = &tmp
	case "stsc":
		var tmp SampleToChunkBody
		v.V = &tmp
	case "stss":
		var tmp SyncSampleBody
		v.V = &tmp
	case "stco":
		var tmp ChunkOffsetBody
		v.V = &tmp
	case "co64":
		var tmp ChunkLargeOffsetBody
		v.V = &tmp
	default:
		var tmp DefaultBody
		v.V = &tmp
	}
	dec.Decode(v.V, info)
}

type SampleDescriptionBody = Directory[Box[DefaultBody]]

type CompositionOffsetBody = Directory[CompositionOffset]
type CompositionOffset struct {
	SampleCount  uint32
	SampleOffset uint32
}

type TimeToSampleBody = Directory[TimeToSample]
type TimeToSample struct {
	SampleCount uint32
	SampleDelta uint32
}

type SampleSizeBody struct {
	SampleSize  uint32
	SampleCount uint32
	EntrySize   []uint32
}

func (v *SampleSizeBody) Unmarshal(dec *Decoder, info *ValueInfo) {
	dec.Decode(&v.SampleSize, &ValueInfo{name: "SampleSize", isBig: true})
	dec.Decode(&v.SampleCount, &ValueInfo{name: "SampleCount", isBig: true})
	if v.SampleSize == 0 {
		v.EntrySize = make([]uint32, v.SampleCount)
		dec.Decode(&v.EntrySize, &ValueInfo{name: "EntrySize", isBig: true})
	}
}

type SampleToChunkBody = Directory[SampleToChunk]
type SampleToChunk struct {
	FirstChunk             uint32
	SamplesPerChunk        uint32
	SampleDescriptionIndex uint32
}

type SyncSampleBody = Directory[SyncSample]
type SyncSample uint32

type ChunkOffsetBody = Directory[ChunkOffset]
type ChunkOffset uint32

type ChunkLargeOffsetBody = Directory[ChunkLargeOffset]
type ChunkLargeOffset uint32
